﻿namespace QuickMail.BusinessLogic
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Threading;

    using QuickMail.DataModels;
    using QuickMail.ViewModels;
    using QuickMail.Properties;
    using DbWorker.AccessModels;
    using DbWorker.DataModels;
    using HigLabo.Mime;
    using HigLabo.Net.Smtp;
    using MailAddress = HigLabo.Mime.MailAddress;
    using MailMessage = HigLabo.Mime.MailMessage;

    class MailsController : IDisposable
    {
        #region Fields

        private const string meta = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n";

        private readonly BackgroundWorker MailCheckerBgw = new BackgroundWorker { WorkerSupportsCancellation = true };
        private readonly BackgroundWorker FoldersCheckerBgw = new BackgroundWorker { WorkerSupportsCancellation = true };

        private IEnumerable<RuleVm> _currentRules;

        private readonly LetterAm _letterAm;
        private readonly FileAm _fileAm;

        private readonly MailsWorker _mailsWorker = new MailsWorker();

        #endregion //Fields

        //public delegate void LetterHandler(LetterDm letterDm, IEnumerable<string> filePaths);
        public delegate void LetterHandler(LetterVm letterVm);
        public event LetterHandler LetterSended;
        public event LetterHandler LetterNotSended;
        public event LetterHandler LetterReceived;

        public MailsController()
        {
            _letterAm = new LetterAm(StaticFields.DbConnection);
            _fileAm = new FileAm(StaticFields.DbConnection);
        }

        /// <summary>
        /// Runs thread of check new mails.
        /// </summary>
        public void RunCheckMails(IEnumerable<RuleVm> ruleVms)
        {
            // ReSharper disable once PossibleMultipleEnumeration
            if (ruleVms == null)
                return;

            // ReSharper disable once PossibleMultipleEnumeration
            _currentRules = ruleVms;

            FoldersCheckerBgw.DoWork += FoldersCheckerBgwOnDoWork;
            FoldersCheckerBgw.RunWorkerCompleted += FoldersCheckerBgwOnRunWorkerCompleted;
            MailCheckerBgw.DoWork += MailCheckerBgwOnDoWork;
            MailCheckerBgw.RunWorkerCompleted += MailCheckerBgwOnRunWorkerCompleted;

            if (!FoldersCheckerBgw.IsBusy)
            {
                FoldersCheckerBgw.RunWorkerAsync();
            }
            else
            {
                StaticFields.Logger.Error(" -- {0}.{1} -- {2}", GetType().FullName,
                    System.Reflection.MethodBase.GetCurrentMethod().Name, "FoldersCheckerBgw.IsBusy == true!");
            }

            if (!MailCheckerBgw.IsBusy)
            {
                MailCheckerBgw.RunWorkerAsync();
            }
            else
            {
                StaticFields.Logger.Error(" -- {0}.{1} -- {2}", GetType().FullName,
                    System.Reflection.MethodBase.GetCurrentMethod().Name, "MailCheckerBgw.IsBusy == true!");
            }
        }

        /// <summary>
        /// Stops checking email.
        /// </summary>
        public void StopCheckMails()
        {
            FoldersCheckerBgw.CancelAsync();
            MailCheckerBgw.CancelAsync();
        }

        /// <summary>
        /// Dispose resources.
        /// </summary>
        public void Dispose()
        {
            MailCheckerBgw.Dispose();
            FoldersCheckerBgw.Dispose();
        }

        #region Methods to check for new messages and folders

        /// <summary>
        /// Scans the folders to send emails in a 2nd thread.
        /// </summary>
        private void FoldersCheckerBgwOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
        {
            while (!FoldersCheckerBgw.CancellationPending)
            {
                foreach (var currentFolder in _currentRules)
                {
                    SendRecursiveScanRules(currentFolder);
                }

                Thread.Sleep(5000);
            }
        }

        /// <summary>
        /// Unsubscribes from the events at the end of check the rules.
        /// </summary>
        private void FoldersCheckerBgwOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
        {
            FoldersCheckerBgw.DoWork -= FoldersCheckerBgwOnDoWork;
            FoldersCheckerBgw.RunWorkerCompleted -= FoldersCheckerBgwOnRunWorkerCompleted;
        }

        /// <summary>
        /// Checks for new messages on the mail.
        /// </summary>
        private void MailCheckerBgwOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
        {
            while (!FoldersCheckerBgw.CancellationPending)
            {
                if (Settings.Default.GetServerType == 0)
                {
                    foreach (var mailMessage in _mailsWorker.ImapCheckNewMails())
                    {
                        foreach (var currentRule in _currentRules)
                        {
                            ReceiveRecursiveScan(currentRule, mailMessage);
                        }
                    }
                }
                else
                {
                    foreach (var mailMessage in _mailsWorker.Pop3CheckNewMails())
                    {
                        foreach (var currentRule in _currentRules)
                        {
                            ReceiveRecursiveScan(currentRule, mailMessage);
                        }
                    }
                }

                Thread.Sleep(Settings.Default.CheckFoldersPeriod);
            }
        }

        /// <summary>
        /// Unsubscribes from the events at the end of check mail.
        /// </summary>
        private void MailCheckerBgwOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
        {
            MailCheckerBgw.DoWork -= MailCheckerBgwOnDoWork;
            MailCheckerBgw.RunWorkerCompleted -= MailCheckerBgwOnRunWorkerCompleted;
        }

        #region Send

        /// <summary>
        /// Recursively scans all the rules and if there is a send action, it is a folder scan.
        /// </summary>
        /// <param name="rule">The current rule.</param>
        private void SendRecursiveScanRules(RuleVm rule)
        {
            if (rule == null)
                return;

            if (rule.Action == RuleAction.Send)
            {
                ScanFolder(rule);
            }

            if (rule.Childrens == null)
                return;

            foreach (var children in rule.Childrens)
            {
                SendRecursiveScanRules(children);
            }
        }

        /// <summary>
        /// Scans a folder on the availability and accessibility of files.
        /// </summary>
        /// <param name="ruleVm">The current rule.</param>
        private void ScanFolder(RuleVm ruleVm)
        {
            string[] filePaths = EnvironmentWorker.GetFiles(ruleVm);
            if (!filePaths.Any())
                return;

            while (!EnvironmentWorker.IsFilesReady(filePaths) ||
                (filePaths.Count() != EnvironmentWorker.GetFiles(ruleVm).Count()))
            {
                Thread.Sleep(3000);
                filePaths = EnvironmentWorker.GetFiles(ruleVm);
            }
            
            var letterDm = new LetterDm
            {
                RuleId = ruleVm.Id,
                From = Settings.Default.SendEmail,
                To = ruleVm.Addresses,
                LetterDate = DateTime.Now,
                Subject = ruleVm.Subject,
                MessageText = meta + ruleVm.MessageText + "\n\n" + Settings.Default.SignedLetters,
            };
            
            var fileDms = new List<FileDm>();

            if (EnvironmentWorker.GetDirectorySize(filePaths) < Settings.Default.MailSize)
            {
                SmtpMessage message = CreateMessage(ruleVm, filePaths);
                
                if (_mailsWorker.SendMessage(message))
                {
                    letterDm.Type = LetterType.Outgoing;
                    _letterAm.Insert(letterDm, out letterDm.Id);

                    foreach (var filePath in filePaths)
                    {
                        var fileDm = CreateFileDm(filePath, letterDm.Id);
                        _fileAm.Insert(fileDm, out fileDm.Id);
                        fileDms.Add(fileDm);
                    }

                    EnvironmentWorker.MoveFiles
                        (filePaths, StaticFields.ResourcesPath + letterDm.Id + "\\");

                    if (LetterSended != null)
                        LetterSended(new LetterVm(letterDm, fileDms));
                }
            }
            else
            {
                StaticFields.Logger.Warn(" -- {0}.{1} -- {2}", GetType().FullName,
                    System.Reflection.MethodBase.GetCurrentMethod().Name, "Too large files!");

                letterDm.Type = LetterType.NotSent;
                _letterAm.Insert(letterDm, out letterDm.Id);
                foreach (var filePath in filePaths)
                {
                    var fileDm = CreateFileDm(filePath, letterDm.Id);
                    _fileAm.Insert(fileDm, out fileDm.Id);
                    fileDms.Add(fileDm);
                }

                EnvironmentWorker.MoveFiles
                    (filePaths, AppDomain.CurrentDomain.BaseDirectory + "Resources\\" + letterDm.Id + "\\");

                if (LetterNotSended != null)
                    LetterNotSended(new LetterVm(letterDm, fileDms));
            }
        }

        #endregion //Send

        #region Receive

        /// <summary>
        /// Scans rules and seeking compliance with the addressee of the letter.
        /// </summary>
        private void ReceiveRecursiveScan(RuleVm folder, MailMessage mailMessage)
        {
            if (folder == null)
                return;

            if (folder.Action == RuleAction.Receive)
            {
                foreach (var address in folder.AddressList)
                {
                    if (address == mailMessage.From.Value)
                    {
                        ReceiveMessage(folder, mailMessage);
                    }
                }
            }

            if (folder.Childrens == null)
                return;

            foreach (var children in folder.Childrens)
            {
                ReceiveRecursiveScan(children, mailMessage);
            }
        }

        /// <summary>
        /// Saves the message and attachments to the directory.
        /// </summary>
        private void ReceiveMessage(RuleVm ruleVm, MailMessage mailMessage)
        {
            try
            {
                if (ruleVm.SaveMessage)
                {
                    //TODO different locales
                    // ReSharper disable once SpecifyACultureInStringConversionExplicitly
                    string fileName = mailMessage.Date.LocalDateTime.ToString().Replace(':', '_') + ".html";
                    SaveMessageTextToFile(Path.Combine(ruleVm.DirectoryPath, fileName), mailMessage);
                }

                List<string> savedFiles = SaveAttachments(ruleVm, mailMessage);

                
                var letterDm = new LetterDm
                {
                    RuleId = ruleVm.Id,
                    //From = mailMessage.From.RawValue,
                    From = ruleVm.Addresses,
                    //To = mailMessage.To.Value,
                    To = Settings.Default.GetEmail,
                    LetterDate = mailMessage.Date.DateTime,
                    Subject = mailMessage.Subject,
                    MessageText = meta + mailMessage.BodyHtml,
                    Type = LetterType.Incoming
                };

                var fileDms = new List<FileDm>();


                _letterAm.Insert(letterDm, out letterDm.Id);

                foreach (var filePath in savedFiles)
                {
                    var fileDm = CreateFileDm(filePath, letterDm.Id);
                    _fileAm.Insert(fileDm, out fileDm.Id);
                    fileDms.Add(fileDm);
                }

                EnvironmentWorker.CopyFiles(savedFiles,
                    AppDomain.CurrentDomain.BaseDirectory + "Resources\\" + letterDm.Id + "\\");

                if (LetterReceived != null)
                    LetterReceived(new LetterVm(letterDm, fileDms));
            }
            catch (Exception ex)
            {
                StaticFields.Logger.Error(" -- {0}.{1} -- {2}", GetType().FullName,
                    System.Reflection.MethodBase.GetCurrentMethod().Name, ex.Message);
            }
        }

        /// <summary>
        /// Saves attachments to the folder.
        /// </summary>
        private List<string> SaveAttachments(RuleVm ruleVm, MailMessage mailMessage)
        {
            var savedFiles = new List<string>();
            if (ruleVm.FilesTypeList == null || !ruleVm.FilesTypeList.Any())
            {
                foreach (MimeContent file in mailMessage.AttachmentFiles)
                {
                    savedFiles.Add(SaveFile(ruleVm, file));
                }
            }
            else
            {
                foreach (var file in mailMessage.AttachmentFiles)
                {
                    if (ruleVm.FilesTypeList.Any(ft => file.Name.Contains(ft.Replace("*", ""))))
                    {
                        savedFiles.Add(SaveFile(ruleVm, file));
                    }
                }
            }

            return savedFiles;
        }

        private string SaveFile(RuleVm ruleVm, MimeContent file)
        {
            string filePath = Path.Combine(ruleVm.DirectoryPath, file.Name);

            if (File.Exists(filePath))
            {
                switch (ruleVm.Replace)
                {
                    case ReplaceFile.Rename:
                        int i = 1;
                        do
                        {
                            string ext = Path.GetExtension(filePath);
                            filePath = filePath.Insert(filePath.IndexOf
                                (ext, StringComparison.Ordinal), " (" + i + ")");
                            i++;
                        }
                        while (File.Exists(filePath));
                        break;
                    case ReplaceFile.Replace:
                        File.Delete(filePath);
                        break;
                    case ReplaceFile.Skip:
                        return filePath;
                }
            }

            file.SaveTo(filePath);
            return filePath;
        }

        /// <summary>
        /// Saves the message text to the file.
        /// </summary>
        private void SaveMessageTextToFile(string filePath, MailMessage mailMessage)
        {
            if (!string.IsNullOrWhiteSpace(mailMessage.BodyHtml.Trim()))
            {
                File.WriteAllText(filePath, meta + mailMessage.BodyHtml);
            }
            else if (!string.IsNullOrWhiteSpace(mailMessage.BodyRawHtml.Trim()))
            {
                File.WriteAllText(filePath, meta + mailMessage.BodyRawHtml);
            }
            else if (!string.IsNullOrWhiteSpace(mailMessage.BodyText.Trim()))
            {
                File.WriteAllText(filePath, meta + mailMessage.BodyText);
            }
        }

        #endregion //Receive

        #endregion //Methods to check for new messages and folders

        #region Other

        /// <summary>
        /// Creates a message based on the rule and file paths.
        /// </summary>
        private static SmtpMessage CreateMessage(RuleVm ruleVm, IEnumerable<string> filePaths)
        {
            var message = new SmtpMessage
            {
                Subject = ruleVm.Subject,
                BodyText = ruleVm.MessageText + "\n\n" + Settings.Default.SignedLetters,
                //From = new MailAddress(string.Format("{0} <{1}>", Settings.Default.FullName, Settings.Default.SendEmail))
                From = new MailAddress(Settings.Default.SendEmail)
            };

            //Fills in recipient list.
            foreach (var address in ruleVm.AddressList)
            {
                message.To.Add(new MailAddress(address));
            }

            //attachs files
            foreach (var file in filePaths)
            {
                // ReSharper disable once UseObjectOrCollectionInitializer
                var content = new SmtpContent();
                content.Name = Path.GetFileName(file);
                content.LoadData(File.ReadAllBytes(file));
                message.Contents.Add(content);
            }

            return message;
        }

        private FileDm CreateFileDm(string filePath, long letterId)
        {
            return new FileDm
            {
                LetterId = letterId,
                FileName = Path.GetFileName(filePath),
                Extention = Path.GetExtension(filePath),
                Hash = HelpWorker.ComputeHash(filePath),
                Size = new FileInfo(filePath).Length
            };

            //foreach (var filePath in filePaths)
            //{
            //    long fileId;
            //    _fileAm.Insert(new FileDm
            //    {
            //        LetterId = letterId,
            //        FileName = Path.GetFileName(filePath),
            //        Extention = Path.GetExtension(filePath),
            //        Hash = HelpWorker.ComputeHash(filePath),
            //        Size = new FileInfo(filePath).Length
            //    }, out fileId);
            //}
        }

        ///// <summary>
        ///// Gets the text from a file or TextBox depending on the settings
        ///// </summary>
        //TODO files encoding
        //private string WriteBodyText(Folder folder)
        //{
        //    if (!folder.MessageFromFile)
        //    {
        //        return folder.MessageText;
        //    }
        //    else
        //    {
        //        string result = string.Empty;
        //        foreach (var file in Directory.GetFiles(folder.FolderPath).
        //            Where(file => Path.GetExtension(file) == ".txt"))
        //        {
        //            result = File.ReadAllText(file).
        //                Aggregate(result, (current, line) => current + line + "\n");
        //            break;
        //        }

        //        return result;
        //    }
        //}

        #endregion //Other
    }
}
